28 — Shader 图案
简介
在创建 shader 时,我们常常需要绘制特定的图案,比如星星、圆形、光晕透镜、波浪等。
这有助于在 geometry 上直观地看到这些图案,也可以用来移动顶点,就像我们在上一课处理旗帜时做的那样。
我们本可以使用 texture,但绘制形状能给我们更多控制权;我们可以对形状参数做动画,而且无需加载 texture。
这比使用 canvas 等其他 API 来绘制要复杂得多,因为每个 fragment 运行的代码都相同,我们拥有的只有坐标和我们自己的数学能力。
是的,这一课会涉及一些数学。对某些人来说这是最令人沮丧的部分之一,但别怕;即使你数学不太好,你也能找到解决办法。
在这一课中,我们将尝试在一个平面上绘制各种图案。我们会从非常细致的部分开始,随后难度会逐渐增加。这是了解经典技巧并使用内置函数的绝佳机会。
对于每个图案,我们先研究结果,然后尝试复现它。如果你想在这方面更上一层楼,请在每个图案处暂停课程,自己动手尝试。即使失败,只要你自己尝试过,解决方案也会更有意义。
设置
目前,scene 上只有一个平面,使用 ShaderMaterial 作为 PlaneGeometry。提醒一下,ShaderMaterial 类似于 RawShaderMaterial,只是在 shader 前面预置了一些代码,比如导入矩阵、导入一些 attribute,或设置精度。
将 UV 坐标发送到 fragment
因为我们要绘制平面图案,所以大部分代码都会放在 fragment shader 中。但首先,我们需要把 UV 坐标从 vertex shader 发送到 fragment shader。
要在 vertex shader 中获取 uv attribute,我们本应写成类似这样:
attribute vec2 uv; 但由于我们使用的是 ShaderMaterial,这段代码已经预置在 vertex shader 中了。
要把这个值从 vertex shader 发送到 fragment shader,我们需要一个 varying。我们将它命名为 vUv,并把 uv 赋值给它:
varying vec2 vUv;
void main()
{
// ...
vUv = uv;
} 在 fragment shader 中,我们可以用相同的声明获取这个 vUv varying:
varying vec2 vUv;
void main()
{
// ...
} 现在我们可以在 fragment shader 中通过 vUv 访问 uv 坐标了。其值从左下角的 0, 0 到右上角的 1, 1。
图案 1
这个漂亮的彩色图案是最容易实现的。我们只需在 gl_FragColor 中使用 vUv,并把蓝色分量设为 1.0:
varying vec2 vUv;
void main()
{
gl_FragColor = vec4(vUv, 1.0, 1.0);
}
图案 2
这个图案完全相同,只是蓝色分量设为 0.0:
varying vec2 vUv;
void main()
{
gl_FragColor = vec4(vUv, 0.0, 1.0);
}
图案 3
这里开始变得有趣一些了。为了得到这个渐变,我们只使用 vUv 的 x 分量,但把它用在 gl_FragColor 的前三个分量上:
varying vec2 vUv;
void main()
{
gl_FragColor = vec4(vUv.x, vUv.x, vUv.x, 1.0);
} 从现在起,我们将绘制这样的黑白图案。与其把值分别赋给 r、g 和 b,我们可以创建一个名为 strength 的 float 变量:
varying vec2 vUv;
void main()
{
float strength = vUv.x;
gl_FragColor = vec4(vec3(strength), 1.0);
} 现在我们将专注于 strength 变量,尝试绘制接下来的图案。
与其替换掉之前的图案,你可以把它注释掉,这样之后还能回到它。
图案 4
这个图案完全相同,只是改用 y 轴:
float strength = vUv.y;
图案 5
这个图案完全相同,只是我们用 1.0 - ... 把值反转了:
float strength = 1.0 - vUv.y;
图案 6
要像这样压缩渐变,我们只需把值相乘。strength 会迅速跳到 1,但颜色不可能比白色更亮,所以渐变的其余部分会保持白色:
float strength = vUv.y * 10.0;
图案 7
这才像话。为了重复渐变,我们使用取模。取模运算求的是第一个数除以第二个数后的余数。
0.5对1.0取模的结果是0.50.8对1.0取模的结果是0.81.2对1.0取模的结果是0.21.7对1.0取模的结果是0.72.0对1.0取模的结果是0.02.4对1.0取模的结果是0.4
就好像第一个数一旦达到第二个数就回到 0 一样。
在许多语言中,我们可以用 % 来做取模,但在 GLSL 中必须使用 mod(...) 函数:
float strength = mod(vUv.y * 10.0, 1.0);
图案 8
这个图案看起来基于上一个,但这里不是渐变,而是 0.0 或 1.0。
我们本可以用 if 语句实现——因为条件语句在 GLSL 中确实能用——但出于性能考虑,我建议避免使用条件语句。
我们可以使用 step(...) 函数。第一个参数是边值,第二个参数是一个数。如果这个数小于边值,结果为 0.0;如果大于边值,结果为 1.0:
float strength = mod(vUv.y * 10.0, 1.0);
strength = step(0.5, strength); 如你所见,我们在另一行使用了 step(...) 函数并重新赋值给 strength。这不会带来性能损耗。你会看到许多 shader 开发者写出巨大的单行代码,尽可能少用变量、几乎不写注释,但那只是因为他们清楚自己在做什么。
随你喜好来做,尤其是如果你是初学者。
图案 9
这个图案与上一个相同,只是 step(...) 的边值更大:
float strength = mod(vUv.y * 10.0, 1.0);
strength = step(0.8, strength);
图案 10
这个图案与上一个相同,只是我们使用了 vUv 的 x 轴而非 y 轴:
float strength = mod(vUv.x * 10.0, 1.0);
strength = step(0.8, strength);
图案 11
我们也可以把它们组合起来。这里,我们需要把 x 轴的结果加到 y 轴的结果上:
float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
strength += step(0.8, mod(vUv.y * 10.0, 1.0));
图案 12
这个图案使用相同的原理,只是改用乘法。我们只能看到它们的相交部分:
float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
strength *= step(0.8, mod(vUv.y * 10.0, 1.0));
图案 13
这个图案与之前相同,只是我们调整了 x 轴上 step 的边值:
float strength = step(0.4, mod(vUv.x * 10.0, 1.0));
strength *= step(0.8, mod(vUv.y * 10.0, 1.0));
图案 14
这个图案是之前几个图案的组合。我们在 x 轴上创建条,再加上 y 轴上的条:
float strength = step(0.4, mod(vUv.x * 10.0, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
strength += step(0.8, mod(vUv.x * 10.0, 1.0)) * step(0.4, mod(vUv.y * 10.0, 1.0)); 就像在任何语言中一样,当代码变得像这样难以忍受时,稍微重构一下是个好主意:
float barX = step(0.4, mod(vUv.x * 10.0, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
float barY = step(0.8, mod(vUv.x * 10.0, 1.0)) * step(0.4, mod(vUv.y * 10.0, 1.0));
float strength = barX + barY;
图案 15
这个图案与之前相同,只是我们对条的 x 和 y 轴应用了一个小的偏移:
float barX = step(0.4, mod(vUv.x * 10.0 - 0.2, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
float barY = step(0.8, mod(vUv.x * 10.0, 1.0)) * step(0.4, mod(vUv.y * 10.0 - 0.2, 1.0));
float strength = barX + barY; 这正是像我们这样的初学者会不断调整数值直到奏效的情况。这没什么问题,而且一旦你找到答案,解决方案大概就会变得合理。
图案 16
这个我们换个方向。为了得到这个结果,我们首先需要偏移 vUv.x,让它从 -0.5 到 0.5。然后我们需要这个值始终为正,于是它从 0.5 到 0.0 再到 0.5。为此,我们可以使用 abs(...) 函数:
float strength = abs(vUv.x - 0.5);
图案 17
这个图案看起来像是把上一个图案与 y 轴上的一个变体组合起来。这可不是普通的组合。你在这里看到的是 x 轴图案与 y 轴图案之间的最小值。为此,我们使用 min(...) 函数:
float strength = min(abs(vUv.x - 0.5), abs(vUv.y - 0.5));
图案 18
与上面相同,只是改用 max(...) 函数:
float strength = max(abs(vUv.x - 0.5), abs(vUv.y - 0.5));
图案 19
对于这个图案,我们只是对上一步的值应用了 step(...):
float strength = step(0.2, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));
图案 20
这个图案是把一个正方形与另一个更小且反转的正方形相乘。
float strength = step(0.2, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));
strength *= 1.0 - step(0.25, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));
图案 21
对于这个图案,我们先把 vUv.x 乘以 10.0,用 floor(...) 函数向下取整,再除以 10.0,从而得到一个介于 0.0 和 1.0 之间的值:
float strength = floor(vUv.x * 10.0) / 10.0;
图案 22
和之前一样,我们可以通过相乘来组合不同的轴:
float strength = floor(vUv.x * 10.0) / 10.0 * floor(vUv.y * 10.0) / 10.0;
图案 23
实现这个图案比较复杂,因为 GLSL 中没有原生的随机函数。诀窍是得到一个难以预测到看起来像随机的值。
获得这种值的一种流行做法是使用以下函数:
float random(vec2 st)
{
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
} 我们向这个函数传入一个 vec2,得到一个伪随机值。
如果你想进一步了解这个函数,这里是来自 The Book of Shaders 的一个链接:https://thebookofshaders.com/10/
我们可以把这个函数加在 main 函数之外,并配合 vUv 使用它:
varying vec2 vUv;
float random(vec2 st)
{
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
void main()
{
// ...
float strength = random(vUv);
// ...
} 使用这个随机函数时要小心。使用错误的数值会让随机结果中出现明显的形状。
图案 24
这个图案是前两个图案的组合。首先,我们创建一个名为 gridUv 的新 vec2 坐标,其值为取整后的结果:
vec2 gridUv = vec2(floor(vUv.x * 10.0) / 10.0, floor(vUv.y * 10.0) / 10.0); 然后,我们把这两个坐标用于 random 函数:
float strength = random(gridUv);
图案 25
这个图案源自上一个。为了得到这种倾斜效果,我们在创建 gridUv 时要把 vUv.x 加到 vUv.y 上:
vec2 gridUv = vec2(floor(vUv.x * 10.0) / 10.0, floor((vUv.y + vUv.x * 0.5) * 10.0) / 10.0);
float strength = random(gridUv);
图案 26
在这个图案中,离左下角越远,strength 就越亮。
实际上这就是 vUv 的长度。vUv 的值在左下角等于 0.0, 0.0,所以长度是 0.0;离那个角越远,它的长度就越大。
我们可以用 length(...) 函数获取一个向量(vec2、vec3 或 vec4)的长度:
float strength = length(vUv);
图案 27
取而代之,我们要获取 vUv 与平面中心之间的距离。因为平面的 UV 从 0.0, 0.0 到 1.0, 1.0,所以中心是 0.5, 0.5。我们将创建一个对应中心的 vec2,并用 distance(...) 函数求出从 vUv 到该中心的距离:
float strength = distance(vUv, vec2(0.5)); 当只用一个值创建向量时,这个值会赋给每一个分量——在我们的情况下就是 x 和 y。
要知道,我们也本可以偏移 vUv 并使用 length(...) 函数。
图案 28
对于这个图案,我们用 1.0 减去上一步的值:
float strength = 1.0 - distance(vUv, vec2(0.5));
图案 29
这个图案在创建光晕透镜效果时很有用。为了得到这个结果,我们从一个很小的值出发,把它除以上一步计算出的距离:
float strength = 0.015 / (distance(vUv, vec2(0.5)));
图案 30
这个图案相同,只是把 UV 压缩并仅在 y 轴上移动:
float strength = 0.15 / (distance(vec2(vUv.x, (vUv.y - 0.5) * 5.0 + 0.5), vec2(0.5)));
图案 31
而这个图案是用相同的公式相乘而来的,只是第二个基于 x 轴:
float strength = 0.15 / (distance(vec2(vUv.x, (vUv.y - 0.5) * 5.0 + 0.5), vec2(0.5)));
strength *= 0.15 / (distance(vec2(vUv.y, (vUv.x - 0.5) * 5.0 + 0.5), vec2(0.5)));
图案 32
实现这个图案相当费力。我们需要让 vUv 坐标围绕中心旋转。做 2D 旋转是 cos(...) 与 sin(...) 的组合,这里我们不再展开。这也是使用函数的好机会。把下面这个函数加在 main 函数之前:
vec2 rotate(vec2 uv, float rotation, vec2 mid)
{
return vec2(
cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x,
cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y
);
} 然后,我们可以用它创建一组新的 UV,命名为 rotatedUV。问题在于我们想正好旋转整个圆的八分之一。遗憾的是,GLSL 中无法访问 π(pi)。
取而代之,我们可以创建一个包含 π 近似值的变量:
float pi = 3.1415926535897932384626433832795; 因为这个变量永远不会改变,我们可以在代码开头把它保存为一个 define:
#define PI 3.1415926535897932384626433832795 define 比变量开销更低,但不能被修改。把 define 写成大写以区别于其他变量是一种好习惯。
然后我们可以把这个 PI 值用作 rotate(...) 函数的第二个参数(角度):
vec2 rotatedUv = rotate(vUv, PI * 0.25, vec2(0.5)); 最后,我们用这个新的 rotatedUV 替换掉 vUv:
float strength = 0.15 / (distance(vec2(rotatedUv.x, (rotatedUv.y - 0.5) * 5.0 + 0.5), vec2(0.5)));
strength *= 0.15 / (distance(vec2(rotatedUv.y, (rotatedUv.x - 0.5) * 5.0 + 0.5), vec2(0.5)));
图案 33
为了绘制那个圆盘,我们把 distance(...) 函数与 step(...) 函数结合使用,并应用一个偏移来控制圆盘的半径:
float strength = step(0.5, distance(vUv, vec2(0.5)) + 0.25); 我们也本可以改变 step(...) 的第一个参数——名为边值(edge)——来控制半径。
图案 34
这个图案与上一个非常接近,只是我们使用 abs(...) 函数来保持值为正:
float strength = abs(distance(vUv, vec2(0.5)) - 0.25);
图案 35
我们可以组合前两个图案来得到一个圆:
float strength = step(0.02, abs(distance(vUv, vec2(0.5)) - 0.25));
图案 36
我们可以用 1.0 - ... 把它反转:
float strength = 1.0 - step(0.01, abs(distance(vUv, vec2(0.5)) - 0.25));
图案 37
这个图案基于上一个,但带有波浪状的扭曲。为了得到这个结果,我们创建一个新的 UV 变量,命名为 wavedUv,并把一个基于 x 轴的 sin(...) 加到 y 值上:
vec2 wavedUv = vec2(
vUv.x,
vUv.y + sin(vUv.x * 30.0) * 0.1
); 然后,我们用那个 wavedUv 取代 vUv:
float strength = 1.0 - step(0.01, abs(distance(wavedUv, vec2(0.5)) - 0.25));
图案 38
对于这个图案,我们也把波浪扭曲应用到 x 轴上:
vec2 wavedUv = vec2(
vUv.x + sin(vUv.y * 30.0) * 0.1,
vUv.y + sin(vUv.x * 30.0) * 0.1
);
float strength = 1.0 - step(0.01, abs(distance(wavedUv, vec2(0.5)) - 0.25));
图案 39
我们只需提高 sin(...) 的频率,就能得到迷幻效果:
vec2 wavedUv = vec2(
vUv.x + sin(vUv.y * 100.0) * 0.1,
vUv.y + sin(vUv.x * 100.0) * 0.1
);
float strength = 1.0 - step(0.01, abs(distance(wavedUv, vec2(0.5)) - 0.25)); 想象一下把它做成动画的样子。
图案 40
这个图案实际上就是 vUv 的角度。要从 2D 坐标得到角度,我们可以使用 atan(...):
float angle = atan(vUv.x, vUv.y);
float strength = angle;
图案 41
这个图案相同,只是对 vUv 加了 0.5 的偏移,以创建围绕中心的角度:
float angle = atan(vUv.x - 0.5, vUv.y - 0.5);
float strength = angle;
图案 42
再一次,这个图案相同,只是角度从 0.0 到 1.0。目前 atan(...) 返回一个介于 -π 和 +π 之间的值。首先,我们可以除以 PI * 2:
float angle = atan(vUv.x - 0.5, vUv.y - 0.5);
angle /= PI * 2.0;
float strength = angle; 我们得到一个从 -0.5 到 0.5 的值。我们只需加上 0.5:
float angle = atan(vUv.x - 0.5, vUv.y - 0.5);
angle /= PI * 2.0;
angle += 0.5;
float strength = angle; 拥有一个正确的角度是玩转圆形图案的好办法。我们会把 angle 的运算合并到一行中,以便更易阅读:
float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
图案 43
这个图案基于我们一开始用取模所做的相同技巧,只是这次作用于 angle:
float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
float strength = mod(angle * 20.0, 1.0);
图案 44
而这个使用了 sin(...):
float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
float strength = sin(angle * 100.0);
图案 45
我们可以用上一步的值来定义我们之前画的圆的半径:
float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
float radius = 0.25 + sin(angle * 100.0) * 0.02;
float strength = 1.0 - step(0.01, abs(distance(vUv, vec2(0.5)) - radius));
图案 46
这个图案叫做 perlin noise。你很可能已经听说过它,如果没有,你也可能在不经意间见过它。perlin noise 在重现云、水、火、地形高低等自然形态时非常关键,但它也可以用来让草或雪在风中摆动。
perlin noise 有许多种算法,结果各异,维度也不同(2D、3D 甚至 4D),有些会自我重复,有些性能更高,等等。
这里有一个 Github gist,列出了我们能找到的一些最流行的 GLSL perlin noise:https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
不过要小心;有些代码可能不会立刻生效,我们马上就会看到。现在我们将测试 Stefan Gustavson 的 Classic Perlin Noise,它是一个 2D noise——我们传入一个 vec2,返回一个 float。先把代码复制到你的 shader 里,但先不要使用它:
// Classic Perlin 2D Noise
// by Stefan Gustavson
//
vec2 fade(vec2 t)
{
return t*t*t*(t*(t*6.0-15.0)+10.0);
}
float cnoise(vec2 P)
{
vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation
vec4 ix = Pi.xzxz;
vec4 iy = Pi.yyww;
vec4 fx = Pf.xzxz;
vec4 fy = Pf.yyww;
vec4 i = permute(permute(ix) + iy);
vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024...
vec4 gy = abs(gx) - 0.5;
vec4 tx = floor(gx + 0.5);
gx = gx - tx;
vec2 g00 = vec2(gx.x,gy.x);
vec2 g10 = vec2(gx.y,gy.y);
vec2 g01 = vec2(gx.z,gy.z);
vec2 g11 = vec2(gx.w,gy.w);
vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11));
g00 *= norm.x;
g01 *= norm.y;
g10 *= norm.z;
g11 *= norm.w;
float n00 = dot(g00, vec2(fx.x, fy.x));
float n10 = dot(g10, vec2(fx.y, fy.y));
float n01 = dot(g01, vec2(fx.z, fy.z));
float n11 = dot(g11, vec2(fx.w, fy.w));
vec2 fade_xy = fade(Pf.xy);
vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
return 2.3 * n_xy;
} 不幸的是,这段代码似乎让 shader 出了问题,这是因为缺少一个名为 permute 的函数。它在这里,你可以把它加在 fade 函数之前:
vec4 permute(vec4 x)
{
return mod(((x*34.0)+1.0)*x, 289.0);
} 现在我们可以访问 cnoise 函数了,可以对它使用 vUv:
float strength = cnoise(vUv); 结果很粗糙,但我们总算有了点东西。想看到预览里那样更多的图案,就把 vUv 乘以 10.0:
float strength = cnoise(vUv * 10.0);
图案 47
这个图案使用相同的 noise,只是在上面加了一个 step:
float strength = step(0.0, cnoise(vUv * 10.0)); 如果哪天你想做一头奶牛,这会非常有用。
图案 48
对于这个图案,我们对值使用了 abs(...),然后用 1.0 减去结果:
float strength = 1.0 - abs(cnoise(vUv * 10.0)); 你可以用它来做闪电、水下倒影或等离子能量之类的东西。
图案 49
对于这个图案,我们对 noise 应用了 sin(...):
float strength = sin(cnoise(vUv * 10.0) * 20.0);
图案 50
而对于这最后一个,我们把 sin(...) 和 step(...) 组合起来:
float strength = step(0.9, sin(cnoise(vUv * 10.0) * 20.0)); 小菜一碟,黑白分明,轻松搞定。
用颜色测试
这很有趣,但这些黑白颜色开始变得单调了。我们一开始有一个很酷的颜色,就是把 vUv 直接用在 gl_FragColor 中:
gl_FragColor = vec4(vUv, 1.0, 1.0); 现在我们可以做的就是用那个渐变色取代白色。
混合颜色
为此,我们将使用 mix(...) 函数。这个函数需要 3 个参数:
- 第一个输入,可以是
float、vec2、vec3或vec4。 - 第二个输入,类型应与第一个相同。
- 第三个值,必须是
float。它决定是多取第一个输入还是多取第二个。如果我们用0.0,返回值就是第一个输入;如果用1.0,返回值就是第二个;如果用0.5,值就是两个输入的混合。你也可以低于0.0或高于1.0,这时值会被外推。
让我们创建第一个颜色:
vec3 blackColor = vec3(0.0); 让我们构造第二个颜色:
vec3 uvColor = vec3(vUv, 1.0); 我们根据 strength 得到两个颜色之间的混合:
vec3 mixedColor = mix(blackColor, uvColor, strength); 然后我们在 gl_FragColor 中使用这个混合结果,不改动 alpha:
gl_FragColor = vec4(mixedColor, 1.0); 尽情用它测试之前所有的图案吧。
修正 strength
如果你用这个 UV 渐变测试 #11、#14 和 #15 等图案,会在相交处看到一些奇怪的表现。
看起来相交处太亮了,而事实确实如此。这是因为我们在 mix(...) 中使用的 strength 值高于 1.0,输出就被外推了——意思是它超出了第二个值。
为了限制这个值,我们可以对 strength 使用 clamp(...) 函数。这个函数只是给一个值设定下限和上限:
// Pattern 11
float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
strength += step(0.8, mod(vUv.y * 10.0, 1.0));
strength = clamp(strength, 0.0, 1.0);
// ...
// Pattern 14
float barX = step(0.4, mod(vUv.x * 10.0, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
float barY = step(0.8, mod(vUv.x * 10.0, 1.0)) * step(0.4, mod(vUv.y * 10.0, 1.0));
float strength = barX + barY;
strength = clamp(strength, 0.0, 1.0);
// Pattern 15
float barX = step(0.4, mod(vUv.x * 10.0 - 0.2, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
float barY = step(0.8, mod(vUv.x * 10.0, 1.0)) * step(0.4, mod(vUv.y * 10.0 - 0.2, 1.0));
float strength = barX + barY;
strength = clamp(strength, 0.0, 1.0);
进一步探索
还有许多其他可能的图案和许多额外的函数。这一课的目的是为你未来的项目备好更多技能,并在一个简单的场景中练习 GLSL。
有一件有用的事我们没有尝试,就是把这些形状封装进函数里。我们本可以创建 getCircle 函数、getSquare 函数等,并配上合适的参数,以便轻松复用。
继续练习,不要害怕创建新的形状,多做实验,需要时就去寻求帮助。
另外,试试添加一些 uniforms 来给这些值做动画,或在调试面板中加一些调节项。